<?php
/**
 * Function:
 * Description:
 * Abo 2019/5/23 18:20
 * Email: abo2013@foxmail.com
 */

namespace Abo\Larasearch\V0\RedisBitMap;

use Illuminate\Support\FacadesRedis;

/**
 * Class RbmBuilder
 * redisBitMap逻辑类
 *
 * 1 初始化( 定时更新 ) 分类-文件 数据 initCategoryBitMap()
 * 2 条件变动时更新 单个素材分类属性    singleUpdateCategoryBitMap()
 * 3 条件操作                       bitOP()
 * 4 解析错ids字符串                 getObjectIdInBitMapV2()
 */
class RbmBuilder
{
    const LOGIC_OP_AND = 'AND';
    const LOGIC_OP_OR = 'OR';
    const LOGIC_OP_NOT = 'NOT';

    private $retIDs, $retIDsCount = 0;

    /**
     * 初始化Bitmap
     * @param $bitmapKey string|array 更新下标 color:red
     * @param $bitmapId  array|int    更新id  1008
     * @return int
     */
    public function initBitmap( $bitmapKey, $bitmapId )
    {
        $syncCount = 0;
        if ( !is_string( $bitmapKey ) || !is_array( $bitmapId ) ) { // 单个分类更新
            return $syncCount;
        }

        $this->delBitmapByKey( $bitmapKey );
        $syncCount = $this->syncTypeBitmap( $bitmapKey, $bitmapId );

        return $syncCount;
    }

    /**
     * 更新 单个素材分类属性
     * @param string $bitmapKey 全分类键值数组 'color:red'
     * @param int $bitmapId
     * @param int $bitmapValue 0, 1
     * @return int
     */
    public function syncUniqueIdBitmap( string $bitmapKey, int $bitmapId, int $bitmapValue = 1 )
    {
        if ( !$bitmapKey || !$bitmapKey || !in_array( $bitmapValue, [ 0, 1 ] ) ) {
            return 0;
        }

        return Redis::setBit( $bitmapKey , $bitmapId , $bitmapValue );
    }

    /**
     * redisBitMap 逻辑处理 $needFlush 是否清空上一次结果集
     * @param string $retName           结果名称
     * @param string $condBitmapName    条件名称
     * @param string $operation         操作
     * @param bool $needFlush
     * @return mixed
     * @throws \Exception
     */
    public function bitOP( $retName, $condBitmapName, $operation = self::LOGIC_OP_AND, $needFlush = false )
    {
        $operationArr = [ self::LOGIC_OP_AND, self::LOGIC_OP_NOT, self::LOGIC_OP_OR ];

        if ( !$retName || !$condBitmapName ) {
            throw new \Exception( '亲...bitmap查询条件不能为空,请重新设置', false );
        }
        if ( !in_array( $operation, $operationArr ) ) {
            throw new \Exception( '亲...查询错误,请重新设置', false );
        }
        if ( !Redis::exists( $condBitmapName ) ) { //
            throw new \Exception( '亲...查询条件为reids值为空,请重新设置~'.$condBitmapName, false );
        }

        if ( $needFlush ) { // 刷新结果
            Redis::del( $retName );
        }
        if ( !Redis::exists( $retName ) ) {
            Redis::set( $retName, Redis::get( $condBitmapName ) );
        }

        return Redis::bitop( $operation, $retName, $retName, $condBitmapName );
    }

    /**
     * 清除 bitmap
     * @param string $cacheKey
     * @return bool
     */
    public function delBitmapByKey( string $cacheKey = '' )
    {
        if ( !$cacheKey ){ return false; }

        $keys = Redis::keys( $cacheKey.'*' );

        if ( !$keys ) { return false; }

        foreach ( $keys as $v2Key ) {
            $tem = Redis::del( $v2Key );
        }

        return true;
    }

    /** 解pack值,带分页 */
    public function getEverPageIdInBitMap(  string $byteMapString, $pageNow, $pageSize = 100  )
    {
        $pageSize <= 0 && $pageSize = 100;
        $pageNow = $pageNow <= 0 ? 1 : $pageNow;
        $pageStart = ($pageNow - 1) * $pageSize;

        $bitmap = unpack('C*', $byteMapString);

        $offset = 0;
        foreach ($bitmap as $byteValue) {
            $this->unpackByte4Page( $byteValue, $offset, $pageStart, $pageSize );
            $offset++;
        }

        return trim( $this->retIDs, ',' );
    }

    /** 解pack值,取bitmap取具体值 */
    public function getObjectIdInBitMapV2( string $byteMapString )
    {
        $bitmap = unpack('C*', $byteMapString);

        $offset = 0;
        foreach ($bitmap as $byteValue) {
            $this->unpackByte( $byteValue, $offset );
            $offset++;
        }

        return trim( $this->retIDs, ',' );
    }

    /**
     * 运算查找
     * @param $str
     * @param $find
     * @param $n
     * @return bool|int
     */
    public function strNPosition($str,$find,$n){
        $pos_val = 0;
        for ($i=1;$i<=$n;$i++){
            $pos = strpos($str,$find);
            $str = substr($str,$pos+1);
            $pos_val=$pos+$pos_val+1;
        }
        return $pos_val-1;
    }

    /**
     * 正则查找
     * @param $str
     * @param $find
     * @param $n
     * @return string
     */
    public function stringNPositionReg($str,$find,$n) {
        preg_match_all("/($find)/",$str,$search_result, PREG_OFFSET_CAPTURE);
        $pos = isset($search_result[1][$n-1][1]) ? $search_result[1][$n-1][1] : "错误! 字符串中有且仅有". count($search_result[1])."个字符:" . $find;
        return $pos;
    }

    /**
     * 更新 单个分类bitmap
     * @param string $bitmapKey     redis下标 建议~color:red
     * @param array $bitmapId       该分类下实体主键 [ 1, 2, 10086 ]
     * @return int
     */
    private function syncTypeBitmap( string $bitmapKey,  array $bitmapId )
    {
        if ( !is_array( $bitmapId ) || !$bitmapId ) { return 0; }
        $i2Count = 0;

        foreach ( $bitmapId as $v2Id ) {
            Redis::setBit( $bitmapKey , $v2Id , 1 );
            $i2Count++;
        }

        return $i2Count;
    }

    /** byte解析成 bit */
    private function unpackByte( $byteValue, $offset )
    {
        $bitlen = 7;
        while ( $byteValue ) {
            if ( $byteValue & 1 == 1 ) {
                $this->retIDs .= ','.($bitlen + $offset * 8);
            }
            $bitlen --;

            $byteValue = $byteValue >> 1;
        }

    }

    /** byte解析成 bit */
    private function unpackByte4Page( $byteValue, $offset, $pageStart, $pageSize )
    {
        $bitlen = 7;
        $pageEnd = $pageStart + $pageSize;

        while ( $byteValue ) {
            if ( $byteValue & 1 == 1
                && $this->retIDsCount >= $pageStart
                && $this->retIDsCount < $pageEnd
            ) {
                $this->retIDs .= ','.($bitlen + $offset * 8);
                $this->retIDsCount ++;
            }
            $bitlen --;

            $byteValue = $byteValue >> 1;
        }
    }
}